package orm

import (
	"database/sql/driver"
	"encoding/json"
	"errors"
	"reflect"
	"time"

	"gddgame.cc/galaxy/utils/def"
)

var (
	ModelEmptyError   = errors.New("model is nil")
	ModelUpdateError  = errors.New("update not support batch")
	NoPrimaryError    = errors.New("Model do not have primary")
	PrimaryEmptyError = errors.New("Model primary empty")
	NoSoftError       = errors.New("schema not have soft delete")
	NextError         = errors.New("next is error")
	NoAutoError       = errors.New("Model do not have autoincrement")
)

type ModelQuery interface {
	Query
	Pk(key ...interface{}) ModelQuery
	Get() (interface{}, error)
	SoftDelete() (int64, error)
	Recovery() (int64, error)
	Save(insert bool, record ...interface{}) (int64, error)
}

func (q *query) setModel(model *Model) *query {
	q.model = model
	q.schema = model.schema
	return q
}

func (q *query) Pk(key ...interface{}) ModelQuery {
	query := q.model.executor.getQuery()
	if q.model.schema.primary == nil {
		return query.error(NoPrimaryError)
	}
	for k, v := range q.model.schema.primary {
		query.Where("=", q.model.schema.fields[v], key[k])
	}
	return query
}

func (q *query) Get() (interface{}, error) {
	defer q.release()
	if q.model == nil {
		return nil, ModelEmptyError
	}
	q.Limit(0, 1)
	schema := q.model.schema

	value := reflect.New(schema.recordType)

	rows, err := q.selectInline()
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	if rows.Next() {
		columns, err := rows.Columns()
		if err != nil {
			return nil, err
		}
		err = scanStruct(q.executor, rows, q.schema, columns, &value)
		return value.Interface(), err
	} else {
		return nil, NextError
	}
}

func (q *query) SoftDelete() (int64, error) {
	defer q.release()
	if q.model == nil {
		return 0, ModelEmptyError
	}
	schema := q.model.schema
	if !schema.softDelete {
		return 0, NoSoftError
	}
	data := make(map[string]interface{})
	data[schema.fields[schema.deleteTime]] = time.Now()
	return q.Update(data)
}

func (q *query) Recovery() (int64, error) {
	defer q.release()
	if q.model == nil {
		return 0, ModelEmptyError
	}
	schema := q.model.schema
	if !schema.softDelete {
		return 0, NoSoftError
	}

	data := make(map[string]interface{})
	data[schema.deleteTime] = 0
	return q.Update(data)
}

func (q *query) Save(insert bool, records ...interface{}) (int64, error) {
	if q.model == nil {
		return 0, ModelEmptyError
	}
	schema := q.model.schema
	primary := schema.primary
	if primary == nil {
		return 0, NoPrimaryError
	}
	auto := schema.GetAutoIncrement()
	var autoValue interface{}
	primaryMap := map[string]interface{}{}
	if auto == "" {
		if !insert {
			primaryMap = make(map[string]interface{}, len(primary))
		}
		for key := range primary {
			for _, record := range records {
				value, err := schema.Get(record, primary[key])
				if err != nil {
					return 0, err
				}
				if value == 0 {
					return 0, PrimaryEmptyError
				}
				if !insert {
					primaryMap[primary[key]] = value
				}
			}
		}
	} else if !insert {
		// 自增长字段为空，添加
		value, err := schema.Get(records[0], auto)
		if err != nil {
			return 0, err
		}
		if value == 0 {
			insert = true
		} else {
			autoValue = value
		}
	}

	if !insert && len(records) > 1 {
		return 0, ModelUpdateError
	}
	if insert {
		data := make([]map[string]interface{}, len(records))
		for index, record := range records {
			d, err := interface2map(schema, record)
			if err != nil {
				return 0, err
			}
			data[index] = d
		}
		result, err := q.Insert(data...)
		if err != nil {
			return 0, err
		}
		if auto != "" {
			id := result
			for i := len(data) - 1; i >= 0; i-- {
				if err := schema.Set(records[i], auto, result); err != nil {
					return 0, err
				}
				id -= 1
			}
		}
		return result, err
	} else {
		data, err := interface2map(schema, records[0])
		if err != nil {
			return 0, err
		}
		if auto != "" {
			q.Where("=", auto, autoValue)
		} else {
			for key, value := range primaryMap {
				q.Where("=", key, value)
			}
		}
		return q.Update(data)
	}
}

func (q *query) modelUpdate(record map[string]interface{}) ([]string, []interface{}, error) {
	if q.model == nil {
		return nil, nil, ModelEmptyError
	}
	schema := q.model.schema
	if schema.updateTime != "" {
		record[schema.fields[schema.updateTime]] = time.Now()
	}
	if err := model2map(q.executor, schema, record); err != nil {
		return nil, nil, err
	}
	fields, d := parseMap(record)
	return fields, d[0], nil
}

func (q *query) modelInsert(records ...map[string]interface{}) ([]string, [][]interface{}, error) {
	if q.model == nil {
		return nil, nil, ModelEmptyError
	}

	schema := q.model.schema
	if schema.createTime != "" {
		for _, record := range records {
			record[schema.fields[schema.createTime]] = time.Now()
		}
	}
	if schema.updateTime != "" {
		for _, record := range records {
			record[schema.fields[schema.updateTime]] = time.Now()
		}
	}
	columns := schema.columns
	for f, ff := range schema.fields {
		//originSlice = append(originSlice, f)
		if f == schema.GetAutoIncrement() {
			// 移除自增长字段
			for _, v := range records {
				delete(v, ff)
			}
			continue
		}
		// 设置默认值
		for _, v := range records {
			if _, ok := v[ff]; !ok {
				v[ff] = columns[ff].d
			}
		}
	}
	for _, v := range records {
		if err := model2map(q.executor, schema, v); err != nil {
			return nil, nil, err
		}
	}
	fields, d := parseMap(records...)
	return fields, d, nil
}

//func formatInsertData(schema *Schema, records ...map[string]interface{}) ([]string, [][]interface{}) {
//	columns := schema.columns
//	fields := schema.fields
//	if len(records) == 0 {
//		return []string{}, [][]interface{}{}
//	}
//	fieldsSlice := make([]string, 0, len(fields))
//	//originSlice := make([]string, 0, len(fields))
//	for f, ff := range fields {
//		//originSlice = append(originSlice, f)
//		if f == schema.AutoIncrement {
//			continue
//		}
//		fieldsSlice = append(fieldsSlice, ff)
//	}
//	d := make([][]interface{}, len(records))
//	for k, v := range records {
//		g := make([]interface{}, len(fieldsSlice))
//		for kk, field := range fieldsSlice {
//			c, ok := v[field]
//			if !ok {
//				c = columns[field].d
//			}
//			g[kk] = c
//		}
//		d[k] = g
//	}
//	return fieldsSlice, d
//}
func model2map(executor *executor, schema *Schema, record map[string]interface{}) error {
	columns := schema.columns
	fields := schema.fields
	for _, colName := range fields {
		var val interface{}
		if _, ok := record[colName]; !ok {
			continue
		}
		val = record[colName]
		value := reflect.ValueOf(val)
		col := columns[colName]

		if value.CanAddr() {
			if structConvert, ok := value.Addr().Interface().(def.Serializer); ok {
				data, err := structConvert.Marshal()
				if err != nil {
					return err
				} else {
					val = data
				}
				goto APPEND
			}
			if _, ok := value.Addr().Interface().(UpdateExtend); ok {
				continue
			}
		}

		if value.Kind() == reflect.Ptr {
			if value.IsNil() {
				goto APPEND
			} else if !value.IsValid() {
				continue
			} else {
				if structConvert, ok := value.Interface().(def.Serializer); ok {
					data, err := structConvert.Marshal()
					if err != nil {
						return err
					} else {
						val = data
					}
					goto APPEND
				}
				if _, ok := value.Interface().(UpdateExtend); ok {
					continue
				}
				// dereference ptr type to instance type
				value = value.Elem()
			}
		}
		switch value.Kind() {
		case reflect.Struct:
			if col.t.ConvertibleTo(TimeType) || col.t.ConvertibleTo(PtrTimeType) {
				t := value.Convert(TimeType).Interface().(time.Time)
				if !t.IsZero() {
					val = formatTime(col.sql, t)
				}
			} else if valNul, ok := value.Interface().(driver.Valuer); ok {
				val, _ = valNul.Value()
				if val == nil {
					continue
				}
			} else if col.IsJson() {
				if col.IsBlob() {
					var bytes []byte
					var err error
					bytes, err = json.Marshal(value.Interface())
					if err != nil {
						return err
					}
					val = bytes
				} else if col.IsText() {
					bytes, err := json.Marshal(value.Interface())
					if err != nil {
						return err
					}
					val = string(bytes)
				}
			} else {
				val = value.Interface()
			}
		case reflect.Array, reflect.Slice, reflect.Map:
			if value == reflect.Zero(col.t) {
				val = value.Interface()
			}
			if value.IsNil() || !value.IsValid() || value.Len() == 0 {
				val = value.Interface()
			}
			if col.IsJson() {
				if col.IsBlob() {
					var bytes []byte
					var err error
					if col.t.Kind() == reflect.Array || col.t.Kind() == reflect.Slice {
						if value.Len() > 0 {
							if col.t.Elem().Kind() == reflect.Uint8 {
								val = value.Bytes()
							} else {
								bytes, err = json.Marshal(value.Interface())
							}
						} else {
							continue
						}
					} else {
						bytes, err = json.Marshal(value.Interface())
						if err != nil {
							return err
						}
						val = bytes
					}
				} else if col.IsText() {
					var bytes []byte
					var err error
					if col.t.Kind() == reflect.Array || col.t.Kind() == reflect.Slice {
						if value.Len() > 0 {
							bytes, err = json.Marshal(value.Interface())
						} else {
							bytes = []byte{}
						}
					} else {
						bytes, err = json.Marshal(value.Interface())
						if err != nil {
							return err
						}
					}
					val = string(bytes)
				}
			}
		case reflect.Bool:
			fallthrough
		case reflect.String:
			if col.IsTime() {
				t, err := str2Time(executor, col, val.(string))
				if err != nil {
					return err
				}
				if !t.IsZero() {
					val = formatTime(col.sql, *t)
					goto APPEND
				}
			}
			fallthrough
		case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64:
			fallthrough
		case reflect.Float32, reflect.Float64:
			fallthrough
		case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
			fallthrough
		default:
			continue
		}
	APPEND:
		record[colName] = val
	}
	return nil
}
func interface2map(schema *Schema, record interface{}) (map[string]interface{}, error) {
	beanValue := reflect.ValueOf(record)
	if beanValue.Kind() != reflect.Ptr {
		return nil, PointerError
	} else if beanValue.Elem().Kind() == reflect.Ptr {
		return nil, PointerRefError
	}
	data := map[string]interface{}{}
	fields := schema.fields
	recordValue := beanValue.Elem()
	for name, colName := range fields {
		value := recordValue.FieldByName(name)
		if !value.IsValid() {
			continue
		}
		data[colName] = value.Interface()
	}
	return data, nil
}
